/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#define USING_LOG_PREFIX SHARE

#include "lib/mysqlclient/ob_mysql_result.h"
#include "lib/mysqlclient/ob_mysql_transaction.h"
#include "observer/ob_server_struct.h"
#include "observer/ob_sql_client_decorator.h"
#include "observer/ob_srv_network_frame.h"
#include "share/ob_autoincrement_service.h"
#include "share/ob_define.h"
#include "share/ob_global_autoinc_service.h"
#include "storage/ls/ob_ls_tx_service.h"
#include "storage/slog/ob_storage_logger.h"
#include "storage/slog/ob_storage_log.h"
#include "storage/slog/ob_storage_log_replayer.h"
#include "storage/tx_storage/ob_ls_service.h"

namespace oceanbase
{
using namespace oceanbase::common;
using namespace oceanbase::share;

namespace share
{

int ObAutoIncCacheNode::init(const uint64_t sequence_value,
                             const uint64_t last_available_value,
                             const uint64_t sync_value)
{
  int ret = OB_SUCCESS;
  if (sequence_value <= 0 || last_available_value < sequence_value || sync_value > sequence_value) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(ret), K(sequence_value), K(last_available_value), K(sync_value));
  } else {
    sequence_value_ = sequence_value;
    last_available_value_ = last_available_value;
    sync_value_ = sync_value;
  }
  return ret;
}

bool ObAutoIncCacheNode::need_fetch_next_node(const uint64_t base_value,
                                              const uint64_t desired_cnt,
                                              const uint64_t max_value) const
{
  bool bret = false;
  if (OB_UNLIKELY(last_available_value_ == max_value)) {
    bret = false;
  } else if (OB_LIKELY(last_available_value_ >= desired_cnt)) {
    uint64_t new_base_value = std::max(base_value, sequence_value_);
    bret = new_base_value > last_available_value_ - desired_cnt + 1;
  } else {
    bret = true;
  }
  return bret;
}

int ObAutoIncCacheNode::update_sequence_value(const uint64_t sequence_value)
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(!is_valid())) {
    ret = OB_NOT_INIT;
    LOG_WARN("update invalid cache is not allowed", K(ret));
  } else if (OB_UNLIKELY(sequence_value > last_available_value_ ||
                         sequence_value < sequence_value_)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(ret), K(sequence_value),
                                 K(sequence_value_), K(last_available_value_));
  } else {
    sequence_value_ = sequence_value;
  }
  return ret;
}

int ObAutoIncCacheNode::update_available_value(const uint64_t available_value)
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(!is_valid())) {
    ret = OB_NOT_INIT;
    LOG_WARN("update invalid cache is not allowed", K(ret));
  } else if (OB_UNLIKELY(available_value < last_available_value_)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(ret), K(available_value), K(last_available_value_));
  } else {
    last_available_value_ = available_value;
  }
  return ret;
}

int ObAutoIncCacheNode::update_sync_value(const uint64_t sync_value)
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(!is_valid())) {
    ret = OB_NOT_INIT;
    LOG_WARN("update invalid cache is not allowed", K(ret));
  } else if (OB_UNLIKELY(sync_value > sequence_value_)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(ret), K(sync_value), K(sequence_value_));
  } else {
    sync_value_ = sync_value;
  }
  return ret;
}

int ObGlobalAutoIncService::init(const ObAddr &addr, ObMySQLProxy *mysql_proxy)
{
  int ret = OB_SUCCESS;
  if (OB_ISNULL(mysql_proxy)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(ret), KP(mysql_proxy));
  } else if (OB_FAIL(inner_table_proxy_.init(mysql_proxy))) {
    LOG_WARN("init inner table proxy failed", K(ret));
  } else if (OB_FAIL(autoinc_map_.create(ObGlobalAutoIncService::INIT_HASHMAP_SIZE,
                                         ObModIds::OB_AUTOINCREMENT,
                                         ObModIds::OB_AUTOINCREMENT))) {
    LOG_WARN("init autoinc_map_ failed", K(ret));
  } else {
    for (int64_t i = 0; i < MUTEX_NUM; ++i) {
      op_mutex_[i].set_latch_id(common::ObLatchIds::AUTO_INCREMENT_GAIS_LOCK);
    }
    self_ = addr;
    is_inited_ = true;
  }
  return ret;
}

int ObGlobalAutoIncService::mtl_init(ObGlobalAutoIncService *&gais)
{
  int ret = OB_SUCCESS;
  ObMySQLProxy *mysql_proxy = GCTX.sql_proxy_;
  const ObAddr &self = GCTX.self_addr();
  ret = gais->init(self, mysql_proxy);
  return ret;
}

void ObGlobalAutoIncService::destroy()
{
  autoinc_map_.destroy();
  inner_table_proxy_.reset();
  ObSpinLockGuard lock(cache_ls_lock_);
  cache_ls_ = NULL;
  is_leader_ = false;
  is_inited_ = false;
}

int ObGlobalAutoIncService::clear()
{
  int ret = OB_SUCCESS;
  if (autoinc_map_.size() > 0) {
    ret = autoinc_map_.clear();
  }
  return ret;
}

int ObGlobalAutoIncService::handle_next_autoinc_request(
    const ObGAISNextAutoIncValReq &request,
    obrpc::ObGAISNextValRpcResult &result)
{
  int ret = OB_SUCCESS;
  const AutoincKey &key = request.autoinc_key_;
  const uint64_t desired_count = request.desired_cnt_;
  bool is_leader = false;
  lib::ObMutex &mutex = op_mutex_[key.hash() % MUTEX_NUM];
  if (OB_UNLIKELY(!is_inited_)) {
    ret = OB_NOT_INIT;
    LOG_WARN("gloabl service is not init", K(ret));
  } else if (OB_UNLIKELY(!request.is_valid())) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(ret), K(request));
  } else if (OB_FAIL(check_leader_(key.tenant_id_, is_leader))) {
    LOG_WARN("check leader failed", K(ret), K(request.sender_), K(self_));
  } else if (OB_UNLIKELY(!is_leader)) {
    ret = OB_NOT_MASTER;
    LOG_WARN("gais service is not leader", K(ret));
  } else if (OB_FAIL(mutex.lock())) {
    LOG_WARN("fail to get lock", K(ret));
  } else {
    ObAutoIncCacheNode cache_node;
    int err = autoinc_map_.get_refactored(key, cache_node);
    LOG_TRACE("begin handle req autoinc request", K(request), K(cache_node));
    if (OB_UNLIKELY(OB_SUCCESS != err && OB_HASH_NOT_EXIST != err)) {
      ret = OB_ERR_UNEXPECTED;
      LOG_WARN("failed to get seq value", K(ret), K(key));
    } else if (OB_UNLIKELY(!cache_node.is_valid() ||
                             cache_node.need_fetch_next_node(
                               request.base_value_, desired_count, request.max_value_))) {
      OZ(fetch_next_node_(request, cache_node));
    }
    if (OB_SUCC(ret)) {
      if (OB_UNLIKELY(!cache_node.is_valid())) {
        ret = OB_ERR_UNEXPECTED;
        LOG_WARN("Unexpected cache node", K(ret), K(cache_node));
      } else {
        const uint64_t start_inclusive = std::max(cache_node.sequence_value_, request.base_value_);
        const uint64_t max_value = request.max_value_;
        uint64_t end_inclusive = 0;
        if (max_value >= request.desired_cnt_ &&
             start_inclusive <= max_value - request.desired_cnt_ + 1) {
          end_inclusive = start_inclusive + request.desired_cnt_ - 1;
          if (OB_UNLIKELY(end_inclusive > cache_node.last_available_value_)) {
            ret = OB_ERR_UNEXPECTED;
            LOG_WARN("unexpected value", K(ret), K(end_inclusive), K(cache_node));
          } else if (OB_UNLIKELY(end_inclusive == cache_node.last_available_value_)) {
            // the cache node is run out
            cache_node.reset();
          } else if (OB_FAIL(cache_node.update_sequence_value(end_inclusive + 1))) {
            LOG_WARN("fail to update sequence value", K(ret), K(cache_node), K(end_inclusive));
          }
        } else if (OB_FAIL(cache_node.update_sequence_value(max_value))) {
          LOG_WARN("fail to update sequence value", K(ret), K(cache_node), K(max_value));
        } else {
          end_inclusive = max_value;
        }
        if (OB_SUCC(ret)) {
          uint64_t sync_value = cache_node.sync_value_;
          if (request.base_value_ != 0 && request.base_value_ - 1 > sync_value) {
            sync_value = request.base_value_ - 1;
          }
          if (OB_FAIL(result.init(start_inclusive, end_inclusive, sync_value))) {
            LOG_WARN("init result failed", K(ret), K(cache_node));
          } else if (OB_FAIL(autoinc_map_.set_refactored(key, cache_node, 1))) {
            LOG_WARN("set autoinc_map_ failed", K(ret));
          }
        }
        LOG_TRACE("after handle req autoinc request", K(request), K(cache_node));
      }
    }
    mutex.unlock();
  }
  return ret;
}

int ObGlobalAutoIncService::handle_curr_autoinc_request(const ObGAISAutoIncKeyArg &request,
                                                        obrpc::ObGAISCurrValRpcResult &result)
{
  int ret = OB_SUCCESS;
  const AutoincKey &key = request.autoinc_key_;
  uint64_t sequence_value = 0;
  uint64_t sync_value = 0;
  bool is_leader = false;
  lib::ObMutex &mutex = op_mutex_[key.hash() % MUTEX_NUM];
  if (OB_UNLIKELY(!is_inited_)) {
    ret = OB_NOT_INIT;
    LOG_WARN("gloabl service is not init", K(ret));
  } else if (OB_UNLIKELY(!request.is_valid())) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(ret), K(request));
  } else if (OB_FAIL(check_leader_(key.tenant_id_, is_leader))) {
    LOG_WARN("check leader failed", K(ret), K(request.sender_), K(self_));
  } else if (OB_UNLIKELY(!is_leader)) {
    ret = OB_NOT_MASTER;
    LOG_WARN("gais service is not leader", K(ret));
  } else if (OB_FAIL(mutex.lock())) {
    LOG_WARN("fail to get lock", K(ret));
  } else {
    ObAutoIncCacheNode cache_node;
    int err = autoinc_map_.get_refactored(key, cache_node);
    LOG_TRACE("start handle get autoinc request", K(request), K(cache_node));
    if (OB_UNLIKELY(OB_SUCCESS != err && OB_HASH_NOT_EXIST != err)) {
      ret = OB_ERR_UNEXPECTED;
      LOG_WARN("failed to get seq value", K(ret), K(key));
    } else if (OB_LIKELY(cache_node.is_valid())) {
      // get autoinc values from cache
      sequence_value = cache_node.sequence_value_;
      sync_value = cache_node.sync_value_;
      // hash not exist or cache node is non-valid, read value from inner table
    } else if (OB_FAIL(read_value_from_inner_table_(key, sequence_value, sync_value))) {
      LOG_WARN("fail to read value from inner table", K(ret));
    }
    if (OB_SUCC(ret) && OB_FAIL(result.init(sequence_value, sync_value))) {
      LOG_WARN("failed to init result", K(ret), K(key), K(cache_node));
    }
    mutex.unlock();
  }
  return ret;
}

int ObGlobalAutoIncService::handle_push_autoinc_request(
    const ObGAISPushAutoIncValReq &request,
    uint64_t &sync_value)
{
  int ret = OB_SUCCESS;
  const AutoincKey &key = request.autoinc_key_;
  bool is_leader = false;
  lib::ObMutex &mutex = op_mutex_[key.hash() % MUTEX_NUM];
  if (OB_UNLIKELY(!is_inited_)) {
    ret = OB_NOT_INIT;
    LOG_WARN("gloabl service is not init", K(ret));
  } else if (OB_UNLIKELY(!request.is_valid())) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(ret), K(request));
  } else if (OB_FAIL(check_leader_(key.tenant_id_, is_leader))) {
    LOG_WARN("check leader failed", K(ret), K(request.sender_), K(self_));
  } else if (OB_UNLIKELY(!is_leader)) {
    ret = OB_NOT_MASTER;
    LOG_WARN("gais service is not leader", K(ret));
  } else if (OB_FAIL(mutex.lock())) {
    LOG_WARN("fail to get lock", K(ret));
  } else {
    ObAutoIncCacheNode cache_node;
    int err = autoinc_map_.get_refactored(key, cache_node);
    LOG_TRACE("start handle push global autoinc request", K(request), K(cache_node));
    if (OB_UNLIKELY(OB_SUCCESS != err && OB_HASH_NOT_EXIST != err)) {
      ret = OB_ERR_UNEXPECTED;
      LOG_WARN("failed to get seq value", K(ret), K(key));
    } else if (OB_UNLIKELY(OB_HASH_NOT_EXIST == err || cache_node.need_sync(request.base_value_))) {
      const bool is_valid_before_sync = cache_node.is_valid();
      if (OB_FAIL(sync_value_to_inner_table_(request, cache_node, sync_value))) {
        LOG_WARN("sync to inner table failed", K(ret));
      } else {
        bool need_update_node_map = cache_node.is_valid() || is_valid_before_sync;
        if (need_update_node_map && OB_FAIL(autoinc_map_.set_refactored(key, cache_node, 1))) {
          LOG_WARN("set autoinc_map_ failed", K(ret));
        }
      }
    } else {
      sync_value = cache_node.sync_value_;
    }
    mutex.unlock();
  }
  return ret;
}

int ObGlobalAutoIncService::handle_clear_autoinc_cache_request(const ObGAISAutoIncKeyArg &request)
{
  int ret = OB_SUCCESS;
  const AutoincKey &key = request.autoinc_key_;
  bool is_leader = false;
  lib::ObMutex &mutex = op_mutex_[key.hash() % MUTEX_NUM];
  if (OB_UNLIKELY(!is_inited_)) {
    ret = OB_NOT_INIT;
    LOG_WARN("gloabl service is not init", K(ret));
  } else if (OB_UNLIKELY(!request.is_valid())) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(ret), K(request));
  } else if (OB_FAIL(check_leader_(key.tenant_id_, is_leader))) {
    LOG_WARN("check leader failed", K(ret), K(request.sender_), K(self_));
  } else if (OB_UNLIKELY(!is_leader)) {
    ret = OB_NOT_MASTER;
    LOG_WARN("gais service is not leader", K(ret));
  } else if (OB_FAIL(mutex.lock())) {
    LOG_WARN("fail to get lock", K(ret));
  } else {
    LOG_TRACE("start clear autoinc cache request", K(request));
    if (OB_FAIL(autoinc_map_.erase_refactored(key))) {
      LOG_WARN("fail to erase autoinc cache map key", K(ret));
    }
    if (ret == OB_HASH_NOT_EXIST) {
      ret = OB_SUCCESS;
    }
    mutex.unlock();
  }
  return ret;
}

int ObGlobalAutoIncService::check_leader_(const uint64_t tenant_id, bool &is_leader)
{
  int ret = OB_SUCCESS;
  is_leader = ATOMIC_LOAD(&is_leader_);
  if (OB_LIKELY(is_leader)) {
  } else {
    // try to get role from logstream
    ObRole role = ObRole::INVALID_ROLE;
    int64_t proposal_id = 0;
    ObSpinLockGuard lock(cache_ls_lock_);
    if (OB_ISNULL(cache_ls_)) {
      ret = OB_NOT_MASTER;
      LOG_WARN("cache ls is null", K(ret));
    } else if (OB_FAIL(cache_ls_->get_log_handler()->get_role(role, proposal_id))) {
      int tmp_ret = ret;
      ret = OB_NOT_MASTER;
      LOG_WARN("get ls role fail", K(ret), K(tmp_ret));
    } else if (common::ObRole::LEADER == role) {
      is_leader = true;
    } else {
      is_leader = false;
    }
  }

  return ret;
}

int ObGlobalAutoIncService::fetch_next_node_(const ObGAISNextAutoIncValReq &request,
                                             ObAutoIncCacheNode &node)
{
  int ret = OB_SUCCESS;
  uint64_t desired_count = std::max(request.cache_size_, request.desired_cnt_);
  uint64_t start_inclusive = 0;
  uint64_t end_inclusive = 0;
  uint64_t sync_value = 0;
  if (OB_FAIL(inner_table_proxy_.next_autoinc_value(request.autoinc_key_,
                                                    request.offset_,
                                                    request.increment_,
                                                    request.base_value_,
                                                    request.max_value_,
                                                    desired_count,
                                                    false, /*for_update*/
                                                    start_inclusive,
                                                    end_inclusive,
                                                    sync_value))) {
    LOG_WARN("fail to require autoinc value from inner table", K(ret));
  } else if (OB_LIKELY(node.is_valid()) && OB_FAIL(node.update_available_value(end_inclusive))) {
    LOG_WARN("fail to update available value", K(ret), K(node), K(end_inclusive));
  } else if (OB_FAIL(node.init(start_inclusive, end_inclusive, sync_value))){
    LOG_WARN("fail to init node", K(ret), K(start_inclusive), K(end_inclusive), K(sync_value));
  } else {
    LOG_TRACE("fetch next node done", K(request), K(node));
  }
  return ret;
}

int ObGlobalAutoIncService::read_value_from_inner_table_(const share::AutoincKey &key,
                                                         uint64_t &sequence_val,
                                                         uint64_t &sync_val)
{
  return inner_table_proxy_.get_autoinc_value(key, sequence_val, sync_val);
}

int ObGlobalAutoIncService::sync_value_to_inner_table_(
    const ObGAISPushAutoIncValReq &request,
    ObAutoIncCacheNode &node,
    uint64_t &sync_value)
{
  int ret = OB_SUCCESS;
  const uint64_t insert_value = request.base_value_;
  uint64_t seq_value = node.is_valid() ? node.sequence_value_ : 0;
  if (OB_FAIL(inner_table_proxy_.sync_autoinc_value(request.autoinc_key_,
                                                    insert_value,
                                                    request.max_value_,
                                                    false, /*for_update*/
                                                    seq_value,
                                                    sync_value))) {
    LOG_WARN("fail to sync autoinc value to inner table", K(ret));
  } else if (OB_LIKELY(node.is_valid())) {
    if (seq_value > node.last_available_value_) {
      // the node is expired.
      node.reset();
    } else if (sync_value == request.max_value_) {
      if (node.last_available_value_ != request.max_value_) {
        ret = OB_ERR_UNEXPECTED;
        LOG_WARN("unexpected error", K(ret), K(node), K(request.max_value_));
      } else if (OB_FAIL(node.init(request.max_value_, request.max_value_, request.max_value_))) {
        LOG_WARN("fail to init node", K(ret), K(request.max_value_));
      }
    } else if (OB_FAIL(node.update_sequence_value(seq_value))) {
      LOG_WARN("fail to update sequence value", K(ret), K(seq_value));
    } else if (OB_FAIL(node.update_sync_value(sync_value))) {
      LOG_WARN("fail to update sync value", K(ret), K(sync_value));
    }
  } else {
    // do nothing for non-valid node.
  }
  return ret;
}

} // share
} // oceanbase
